<?php
/**
 * Copyright 2012  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * Repository
 * 
 * The ESCAPE repository.
 */
class escape_repository_Repository
{
	/**
	 * Fedora instance.
	 * @var fedora_Fedora
	 */
	public $fedora;
	
	/**
	 * Repository schema instance.
	 * @var escape_rdf_RepositorySchemaDocument
	 */
	public $repositorySchema;
	
	/**
	 * Triple store instance.
	 * @var triplestore_TripleStore
	 */
	public $tripleStore;

	/**
	 * ESCAPE configuration
	 * @var array
	 */
	public $config;
	
	public function __construct($config)
	{
		$this->config = $config;
	}
	
	/**
	 * Initialize the repository connection.
	 */
	public function initialize()
	{
		// fetch the repository schema
		$repositorySchemaPath = $this->config['repository_schema_path'];

		$cachePath = $this->config['cache_path'];
		$repositorySchemaCachePath = $cachePath . 'repository_schema/';
		$repositorySchemaCacheFilePath = $repositorySchemaCachePath . $this->config['virtualhost'] . '.rdf.ser';
		
		// determine last modification date of the schema source files and add individual file names to an array
		$configMTime = filemtime($repositorySchemaPath);
		$repositorySchemaFilePaths = array();
		$repositorySchemaDirectory = opendir($repositorySchemaPath);
		while($entryName = readdir($repositorySchemaDirectory))
		{
			if($entryName !== '.' && $entryName !== '..')
			{
				$filePath = $repositorySchemaPath . $entryName;
				$repositorySchemaFilePaths[] = $filePath;
				
				$fileMTime = filemtime($filePath);
				if($fileMTime > $configMTime)
				{
					$configMTime = $fileMTime;
				}
			}
		}
		closedir($repositorySchemaDirectory);
		
		// check if we can use the cached (serialized) version of the schema
		$configCacheMTime = @filemtime($repositorySchemaCacheFilePath);
		if($configCacheMTime !== false && $configCacheMTime > $configMTime)
		{
			// the config cache file exists and is newer than the config file
			$this->repositorySchema = unserialize(file_get_contents($repositorySchemaCacheFilePath));
		}
		else
		{
			// the config cache file doesn't exist or is older than the config file
			//print 'RELOADING SCHEMA';
			
			//create an empty schema document
			$this->repositorySchema = escape_rdf_RepositorySchemaDocument::createRepositorySchemaDocument();
			
			// read all schema files
			foreach($repositorySchemaFilePaths as $filePath)
			{
				// fetch the file content
				$fileContent = file_get_contents($filePath);
				
				// merge it into the repository schema document
				$this->repositorySchema->load($fileContent);
			}

			// preprocess the schema
			$this->repositorySchema->preprocessSchema();
			
			// make sure that the respository schema cache folder exists
			@mkdir($repositorySchemaCachePath, 0777, true);
			
			// create a new cache file
			file_put_contents($repositorySchemaCacheFilePath, serialize($this->repositorySchema));
		}
		
		// get a fedora instance
		$this->fedora = new fedora_Fedora($this->config['fedora']);
		
		// get a triple store instance
		$this->tripleStore = triplestore_TripleStore::getImplementation($this->config['triplestore']['implementation_class'], $this->config['triplestore']['params']);
		
		// initialize the triple store
		$this->tripleStore->initialize();
	}
	
	/**
	 * Close the repository connection.
	 */
	public function shutdown()
	{
		$this->tripleStore->shutdown();
	}
	
	/**
	 * Get the Fedora instance.
	 * @return fedora_Fedora Fedora instance
	 */
	public function &getFedora()
	{
		return $this->fedora;
	}
	
	/**
	 * Get the triple store instance.
	 * @return triplestore_TripleStore Triple store instance
	 */
	public function &getTripleStore()
	{
		return $this->tripleStore;
	}
	
	/**
	 * Rebuild resource URIs.
	 */
	public function rebuildResourceUris()
	{
		$status = '';
		
		// fetch all object URIs for active ORE objects stored in fedora
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "from <#ri>\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.Object> .\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}";
		
		$sparqlResult = $this->fedora->queryRiSparql($sparqlQuery);
		foreach($sparqlResult as $row)
		{
			$uri = $row['uri'];
			
			try
			{
				$oreObject = $this->getOreObjectByUri($uri);
	
				// compose a new resource URI
				$newResourceUri = $this->config['server_url'] . '/resource/' . $oreObject->getId();
				if($oreObject instanceof escape_repository_OreAggregation)
				{
					$newResourceUri = $this->config['server_url'] . '/aggregation/' . $oreObject->getId();
				}
	
				// fetch the old external URI and resource URI
				$externalUri = $oreObject->getExternalUri();
				$resourceUri = $oreObject->getResourceUri();
				
				if($newResourceUri !== $resourceUri)
				{
					// the old and new resource URI are not identical, we need to modify this object
					
					// get the semantic properties
					$rdfDocument = $oreObject->getProperties();
	
					if($externalUri === $resourceUri)
					{
						// the old external URI and resource URI are identical, also update the external URI
						$status .= "Updating external URI for : " . $uri . "\n";
						$rdfDocument->replaceObject('http://purl.utwente.nl/ns/escape-system.owl#externalUri', $externalUri, $newResourceUri);
					}
					
					if($resourceUri === null)
					{
						// the resource URI was previously missing, add it
						$status .= "Adding resource URI for : " . $uri . "\n";
						$rdfDocument->addStatement('http://purl.utwente.nl/ns/escape-system.owl#resourceUri', $newResourceUri);
					}
					else
					{
						// update the resource URI
						$status .= "Updating resource URI for : " . $uri . "\n";
						$rdfDocument->replaceObject('http://purl.utwente.nl/ns/escape-system.owl#resourceUri', $resourceUri, $newResourceUri);
					}
					
					// store the semantic properties
					$oreObject->setProperties($rdfDocument);
				}	
			}
			catch(Exception $exception)
			{
				$status .= "An '" . get_class($exception) . "' exception occurred while processing: " . $uri . "\n";
				$status .= "Exception details: " . $exception->getCode() . " - " . $exception->getMessage() . "\n";
			}		
		}
		
		$status .= "Done\n";
		
		return $status;
	}
	
	/**
	 * Check object consistency.
	 */
	public function checkObjectConsistency()
	{
		$status = '';
		
		// fetch all object URIs for active ORE objects stored in fedora
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "from <#ri>\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.Object> .\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}";
		
		$sparqlResult = $this->fedora->queryRiSparql($sparqlQuery);
		foreach($sparqlResult as $row)
		{
			$uri = $row['uri'];
			
			try
			{
				$oreObject = $this->getOreObjectByUri($uri);
				$properties = $oreObject->getProperties();
				
				if(count($oreObject->getRdfTypes()) == 0)
				{
					$status .= "No RDF types found for object: " . $uri . "\n";
					$status .= "Object properties:\n";
					$status .= $properties->getRdfXml() . "\n";
				}
				
				if(count($properties->getObjects("http://purl.utwente.nl/ns/escape-system.owl#externalUri")) > 1)
				{
					$status .= "Multiple external URI statements found for object: " . $uri . "\n";
					$status .= "The system will use: " . $oreObject->getExternalUri() . "\n";
					$status .= "Object properties:\n";
					$status .= $properties->getRdfXml() . "\n";

//					$status .= "Removing the resource URI from the external URIs\n";
//					$properties->removeStatement("http://purl.utwente.nl/ns/escape-system.owl#externalUri", $oreObject->getResourceUri());
//					$status .= "Object properties:\n";
//					$status .= $properties->getRdfXml() . "\n";
//					$oreObject->setProperties($properties);
				}
				
				if($oreObject instanceof escape_repository_OreAggregation)
				{
					try
					{
						$aggregatedResources = $oreObject->getAggregatedResources();
					}
					catch(Exception $exception)
					{
						$status .= "An '" . get_class($exception) . "' exception occurred while fetching the aggregated resources for aggregation: " . $uri . "\n";
						$status .= "Exception details: " . $exception->getCode() . " - " . $exception->getMessage() . "\n";
					}		
				}
				else {
					try
					{
						$aggregation = $oreObject->getAggregation();
					}
					catch(Exception $exception)
					{
						$status .= "An '" . get_class($exception) . "' exception occurred while fetching the aggregation for aggregated resource: " . $uri . "\n";
						$status .= "Exception details: " . $exception->getCode() . " - " . $exception->getMessage() . "\n";
					}		
				}
			}
			catch(Exception $exception)
			{
				$status .= "An '" . get_class($exception) . "' exception occurred while fetching object: " . $uri . "\n";
				$status .= "Exception details: " . $exception->getCode() . " - " . $exception->getMessage() . "\n";
			}		
		}
		
		$status .= "Done\n";
		
		return $status;
	}
	
	/**
	 * Rebuild all indexes based on the data stored in Fedora.
	 */
	public function rebuildIndexes()
	{
		$status = '';
		
		// remove existing triples
//		$this->tripleStore->removeFromGraph(null, null, null);
		
		// fetch a list of all objects currently in the index and delete them
		$sparqlQuery  = "select distinct ?g\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	graph ?g { ?s ?p ?o }\n";
		$sparqlQuery .= "	filter\n";
		$sparqlQuery .= "	(\n";
		$sparqlQuery .= "		regex(str(?g), '^info:fedora/" . $this->config['data_object_namespace'] . ":[^/]*$')\n";
		$sparqlQuery .= "		||\n";
		$sparqlQuery .= "		regex(str(?g), '^info:fedora/" . $this->config['user_object_namespace'] . ":[^/]*$')\n";
		$sparqlQuery .= "	)\n";
		$sparqlQuery .= "}\n";
		
		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		foreach($results as $result)
		{
			$graphUri = $result['g']['value']; 
			$this->tripleStore->deleteGraph($graphUri);
		}
		
		// fetch all object URIs for active user objects stored in fedora
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "from <#ri>\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.escape.User> .\n";
//		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}";
		
		$sparqlResult = $this->fedora->queryRiSparql($sparqlQuery);
		foreach($sparqlResult as $row)
		{
			$uri = $row['uri'];
			$object = $this->getUserByUri($uri, true, true);

			// reindex the object
			$status .= "Reindexing user: " . $uri . "\n";
			$object->updateIndex();
		}

		// fetch all object URIs for active aggregation objects stored in fedora
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "from <#ri>\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.Aggregation> .\n";
//		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}";
		
		$sparqlResult = $this->fedora->queryRiSparql($sparqlQuery);
		foreach($sparqlResult as $row)
		{
			$uri = $row['uri'];
			$object = $this->getOreAggregationByUri($uri, null, true, true);

			// reindex the object
			$status .= "Reindexing aggregation: " . $uri . "\n";
			$object->updateIndex();
		}

		// fetch all object URIs for active aggregated resource objects stored in fedora
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "from <#ri>\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.AggregatedResource> .\n";
//		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}";
		
		$sparqlResult = $this->fedora->queryRiSparql($sparqlQuery);
		foreach($sparqlResult as $row)
		{
			$uri = $row['uri'];
			$object = $this->getOreAggregatedResourceByUri($uri, null, true, true);

			// reindex the object
			$status .= "Reindexing aggregated resource: " . $uri . "\n";
			$object->updateIndex();
		}
		
		$status .= "Done\n";
		
		return $status;
	}

	/**
	 * Purge ALL objects in this ESCAPE instance. 
	 */
	public function purgeAll()
	{
		$status = '';
		
		// remove existing triples
//		$this->tripleStore->removeFromGraph(null, null, null);
		
		// fetch a list of all objects currently in the index and delete them
		$sparqlQuery  = "select distinct ?g\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	graph ?g { ?s ?p ?o }\n";
		$sparqlQuery .= "	filter\n";
		$sparqlQuery .= "	(\n";
		$sparqlQuery .= "		regex(str(?g), '^info:fedora/" . $this->config['system_object_namespace'] . ":[^/]*$')\n";
		$sparqlQuery .= "		||\n";
		$sparqlQuery .= "		regex(str(?g), '^info:fedora/" . $this->config['data_object_namespace'] . ":[^/]*$')\n";
		$sparqlQuery .= "		||\n";
		$sparqlQuery .= "		regex(str(?g), '^info:fedora/" . $this->config['user_object_namespace'] . ":[^/]*$')\n";
		$sparqlQuery .= "	)\n";
		$sparqlQuery .= "}\n";
		
		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		foreach($results as $result)
		{
			$graphUri = $result['g']['value']; 
			$this->tripleStore->deleteGraph($graphUri);
			
			$pid = $this->fedora->convertUriToPid($graphUri);
			$this->fedora->purgeObject($pid);
		}
		
		$status .= "Done\n";
		
		return $status;
	}
	
	/**
	 * Check if the URI specified is an aggregation
	 * @param string $uri object URI
	 * @return boolean true if the URI specified is an aggregation
	 */
	public function isOreAggregation($uri)
	{
		// check if the URI specified is an aggregation
		$sparqlQuery  = "ask\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	<" . $this->tripleStore->sparqlEscapeUri($uri). "> <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.Aggregation> .\n";
		$sparqlQuery .= "	<" . $this->tripleStore->sparqlEscapeUri($uri). "> <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}\n";
		
		$sparqlResultSet = $this->tripleStore->sparqlAsk($sparqlQuery);
		return $sparqlResultSet->getBoolean();
	}
	
	/**
	 * Get an ORE object by object URI.
	 * @param string $uri Object URI
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @return escape_repository_OreObject ORE object
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 */
	public function getOreObjectByUri($uri, $rdfResource = null)
	{
		$oreObject = null;

		if($rdfResource === null)
		{
			// no prefetched RDF data was provided for this request, fetch the data from the index 
			$rdfDocument = $this->tripleStore->fetchStatements($uri, null, null, false, null);
			if($rdfDocument->getResourceCount() > 0)
			{
				$rdfResource = $rdfDocument->getResource(0);
			}
			else
			{
				throw new escape_repository_exception_NotFound('Object "' . $uri . '" does not exist');
			}
		}

		// extract the model URIs
		$modelUris = $rdfResource->getObjects('info:fedora/fedora-system:def/model#hasModel');
		
		// decode the object type and create an object instance
		if(in_array('info:fedora/' . $this->config['system_object_namespace'] . ':cmodel.ore.AggregatedResource', $modelUris))
		{
			$oreObject = $this->getOreAggregatedResourceByUri($uri, $rdfResource);
		}
		else if(in_array('info:fedora/' . $this->config['system_object_namespace'] . ':cmodel.ore.Aggregation', $modelUris))
		{
			$oreObject = $this->getOreAggregationByUri($uri, $rdfResource);
		}
		else
		{
			throw new escape_repository_exception_TypeMismatch("The specified resource is not a ORE Object");
		}
		
		return $oreObject;
	}
	
	/**
	 * Get an ORE object by PID.
	 * @param string $pid PID
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @return escape_repository_OreAggregatedResource|escape_repository_OreAggregation ORE object
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 * @deprecated use getOreObjectByUri() instead
	 */
	public function getOreObjectByPid($pid, $rdfResource = null)
	{
		$uri = $this->fedora->convertPidToUri($pid);
		return $this->getOreObjectByUri($uri, $rdfResource);
	}
	
	/**
	 * Get an ORE aggregation by object URI. Throws an exception if the object is not found or of the wrong type.
	 * @param string $uri Object URI
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @param boolean $noIndex If true, the object is fetched without using the index.
	 * @param boolean $allowDeleted If true, also return deleted objects
	 * @return escape_repository_OreAggregation ORE aggregation
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 */
	public function getOreAggregationByUri($uri, $rdfResource = null, $noIndex = false, $allowDeleted = false)
	{
		return new escape_repository_OreAggregation($this, $uri, $rdfResource, $noIndex, $allowDeleted);
	}
	
	/**
	 * Get an ORE aggregation by PID. Throws an exception if the object is not found or of the wrong type.
	 * @param string $pid PID
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @param boolean $noIndex If true, the object is fetched without using the index.
	 * @return escape_repository_OreAggregation ORE aggregation
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 * @deprecated use getOreAggregationByUri() instead
	 */
	public function getOreAggregationByPid($pid, $rdfResource = null, $noIndex = false)
	{
		$uri = $this->fedora->convertPidToUri($pid);
		return $this->getOreAggregationByUri($uri, $rdfResource, $noIndex);
	}
	
	/**
	 * Get all ORE aggregations.
	 * @return array List of ORE aggregations
	 */
	public function getAllOreAggregations()
	{
		$aggregations = array();
		
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.Aggregation> .\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}";

		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		foreach($results as $result)
		{
			$uri = $result['uri']['value']; 
			$aggregations[] = $this->getOreAggregationByUri($uri);
		}
		
		return $aggregations;
	}
	
	/**
	 * Get the latest ORE aggregations.
	 * @param string $limit limit
	 * @return array List of ORE aggregations
	 */
	public function getLatestOreAggregations($limit)
	{
		$aggregations = array();
		
		$limit = @intval($limit);
		
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.Aggregation> .\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#createdDate> ?createdDate  .\n";
		$sparqlQuery .= "	?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}\n";
		$sparqlQuery .= "order by desc(?createdDate)\n";
		$sparqlQuery .= "limit " . $limit;
		
		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		foreach($results as $result)
		{
			$uri = $result['uri']['value']; 
			$aggregations[] = $this->getOreAggregationByUri($uri);
		}
		
		return $aggregations;
	}
	
	/**
	 * Get an ORE aggregated resource by object URI.
	 * @param string $uri Object URI
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @param boolean $noIndex If true, the object is fetched without using the index.
	 * @param boolean $allowDeleted If true, also return deleted objects
	 * @return escape_repository_OreAggregatedResource ORE aggregated resource
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 */
	public function getOreAggregatedResourceByUri($uri, $rdfResource = null, $noIndex = false, $allowDeleted = false)
	{
		return new escape_repository_OreAggregatedResource($this, $uri, $rdfResource, $noIndex, $allowDeleted);
	}

	/**
	 * Get an ORE aggregated resource by PID.
	 * @param string $pid PID
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @param boolean $noIndex If true, the object is fetched without using the index.
	 * @return escape_repository_OreAggregatedResource ORE aggregated resource
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 * @deprecated use getOreAggregatedResourceByUri() instead
	 */
	public function getOreAggregatedResourceByPid($pid, $rdfResource = null, $noIndex = false)
	{
		$uri = $this->fedora->convertPidToUri($pid);
		return $this->getOreAggregatedResourceByUri($uri, $rdfResource, $noIndex);
	}

	/**
	 * Get Relation Annotations by subject / predicate / object.
	 * @param string $subject Subject URI
	 * @param string $predicate Predicate URI
	 * @param string $object Object URI
	 * @return array ORE aggregated resources
	 */
	public function getRelationAnnotationsBySubjectPredicateObject($subject, $predicate, $object)
	{
		$relationAnnotations = array();
		 
		$sparqlQuery  = "select ?uri\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	{\n";
		$sparqlQuery .= "		?uri <http://purl.utwente.nl/ns/escape-annotations.owl#subject> <" . $this->tripleStore->sparqlEscapeUri($subject). "> .\n";
		$sparqlQuery .= "		?uri <http://purl.utwente.nl/ns/escape-annotations.owl#predicate> <" . $this->tripleStore->sparqlEscapeUri($predicate). "> .\n";
		$sparqlQuery .= "		?uri <http://purl.utwente.nl/ns/escape-annotations.owl#object> \"" . $this->tripleStore->sparqlEscapeLiteral($object) . "\" .\n";
		$sparqlQuery .= "		?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "	}\n";
		if(escape_isValidUri($object))
		{
			$sparqlQuery .= "	union\n";
			$sparqlQuery .= "	{\n";
			$sparqlQuery .= "		?uri <http://purl.utwente.nl/ns/escape-annotations.owl#subject> <" . $this->tripleStore->sparqlEscapeUri($subject). "> .\n";
			$sparqlQuery .= "		?uri <http://purl.utwente.nl/ns/escape-annotations.owl#predicate> <" . $this->tripleStore->sparqlEscapeUri($predicate). "> .\n";
			$sparqlQuery .= "		?uri <http://purl.utwente.nl/ns/escape-annotations.owl#object> <" . $this->tripleStore->sparqlEscapeUri($object) . "> .\n";
			$sparqlQuery .= "		?uri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
			$sparqlQuery .= "	}\n";
		}
		$sparqlQuery .= "}";

		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		foreach($results as $result)
		{
			$uri = $result['uri']['value']; 
			$relationAnnotations[] = $this->getOreAggregatedResourceByUri($uri);
		}
		
		return $relationAnnotations;
	}
	
	/**
	 * Create a new data object (for repository internal use only!).
	 * Warning: this method does not update the index! You will have to fetch the object using the $noIndex parameter and call updateIndex() on it to update the index.
	 * @param string $label Label
	 * @param string $pid PID
	 * @param array $models Fedora model URI's (RELS-EXT)
	 * @param simplerdf_RdfResource $semanticsRdfResource RDF properties (Semantics), the subject URI can be set to # if unavailable
	 * @param simplerdf_RdfResource $relsExtRdfResource RDF properties (RELS-EXT), the subject URI can be set to # if unavailable
	 * @return string PID of the new data object
	 */
	public function _createDataObject($label, $models, $pid = null, $semanticsRdfResource = null, $relsExtRdfResource = null)
	{
		// generate a new PID if no PID was specified
		if($pid === null)
		{
			$pid = $this->fedora->getNextPid(1, $this->config['data_object_namespace']);
		}

		// convert the PID to a URI, this will also perform a check to see if the PID is valid or not
		$uri = $this->fedora->convertPidToUri($pid);
		
		// generate the RELS-EXT datastream content
		if($relsExtRdfResource === null)
		{
			$relsExtRdfResource = simplerdf_RdfResource::createRdfResource($uri);
		}
		else
		{
			$relsExtRdfResource->setSubject($uri);
		}
		foreach($models as $model)
		{
			$relsExtRdfResource->addStatement("info:fedora/fedora-system:def/model#hasModel", $model);
		}
		$relsExtRdfXml = $relsExtRdfResource->getRdfXml(true);
		
		// generate an empty RDF resource if no semantic RDF statements were supplied 
		if($semanticsRdfResource === null)
		{
			$semanticsRdfResource = simplerdf_RdfResource::createRdfResource($uri);
			
		}
		else
		{
			$semanticsRdfResource->setSubject($uri);
		}
		$semanticsRdfXml = $semanticsRdfResource->getRdfXml(true);
		
		// generate a FOXML object
		$objectXml = '<?xml version="1.0" encoding="UTF-8"?>
<foxml:digitalObject PID="' . htmlspecialchars($pid) . '" VERSION="1.1" xmlns:foxml="info:fedora/fedora-system:def/foxml#"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="info:fedora/fedora-system:def/foxml# http://www.fedora.info/definitions/1/0/foxml1-1.xsd">
  <foxml:objectProperties>
    <foxml:property NAME="info:fedora/fedora-system:def/model#state" VALUE="Active"/>
    <foxml:property NAME="info:fedora/fedora-system:def/model#label" VALUE="' . htmlspecialchars($label) . '"/>
    <foxml:property NAME="info:fedora/fedora-system:def/model#ownerId" VALUE="' . htmlspecialchars($this->config['fedora']['username']) . '"/>
  </foxml:objectProperties>
  <foxml:datastream CONTROL_GROUP="X" ID="DC" STATE="A" VERSIONABLE="true">
    <foxml:datastreamVersion FORMAT_URI="http://www.openarchives.org/OAI/2.0/oai_dc/"
      ID="DC.0" LABEL="Dublin Core Record for this object" MIMETYPE="text/xml">
      <foxml:xmlContent>
        <oai_dc:dc xmlns:dc="http://purl.org/dc/elements/1.1/"
          xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd">
          <dc:title>' . htmlspecialchars($label) . '</dc:title>
          <dc:identifier>' . htmlspecialchars($pid) . '</dc:identifier>
        </oai_dc:dc>
      </foxml:xmlContent>
    </foxml:datastreamVersion>
  </foxml:datastream>
  <foxml:datastream CONTROL_GROUP="X" ID="RELS-EXT" STATE="A" VERSIONABLE="true">
    <foxml:datastreamVersion FORMAT_URI="info:fedora/fedora-system:FedoraRELSExt-1.0"
      ID="RELS-EXT.0" LABEL="RDF Statements about this object" MIMETYPE="application/rdf+xml">
      <foxml:xmlContent>' . $relsExtRdfXml . '</foxml:xmlContent>
    </foxml:datastreamVersion>
  </foxml:datastream>
  <foxml:datastream CONTROL_GROUP="X" ID="SEMANTICS" STATE="A" VERSIONABLE="true">
    <foxml:datastreamVersion
      ID="SEMANTICS.0" LABEL="Semantic RDF Statements about this object" MIMETYPE="application/rdf+xml">
      <foxml:xmlContent>' . $semanticsRdfXml . '</foxml:xmlContent>
    </foxml:datastreamVersion>
  </foxml:datastream>
</foxml:digitalObject>';

		// ingest the object into fedora
		$resultPid = $this->fedora->ingest($objectXml, "info:fedora/fedora-system:FOXML-1.1");

		return $resultPid;
	}

	/**
	 * Creates a new ORE Aggregation. 
	 * @param string $aggregationUri Aggregation URI
	 * @return escape_repository_OreAggregation
	 */
	public function createOreAggregation($aggregationUri = null)
	{
		// generate a new PID if no PID was specified
		if($aggregationUri === null)
		{
			$aggregationUri = $this->fedora->convertPidToUri($this->fedora->getNextPid(1, $this->config['data_object_namespace']));
		}
		$aggregationId = $this->convertObjectUriToDataObjectId($aggregationUri);
		$aggregationPid = $this->fedora->convertUriToPid($aggregationUri);
		
		// create an aggregation
		$semanticsRdfResource = simplerdf_RdfResource::createRdfResource($aggregationUri);
		$semanticsRdfResource->addStatement('http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.openarchives.org/ore/terms/Aggregation');
		$semanticsRdfResource->addStatement("http://purl.utwente.nl/ns/escape-system.owl#externalUri", $this->config['server_url'] . '/aggregation/' . $aggregationId);
		$semanticsRdfResource->addStatement("http://purl.utwente.nl/ns/escape-system.owl#resourceUri", $this->config['server_url'] . '/aggregation/' . $aggregationId);
		
		$aggregationPid = $this->_createDataObject("ORE Aggregation", array('info:fedora/' . $this->config['system_object_namespace'] . ':cmodel.ore.Object', 'info:fedora/' . $this->config['system_object_namespace'] . ':cmodel.ore.Aggregation'), $aggregationPid, $semanticsRdfResource);
		
		// fetch the aggregation without using the index
		$aggregation = new escape_repository_OreAggregation($this, $aggregationUri, null, true);
		
		// update the index
		$aggregation->updateIndex();
		
		return $aggregation;
	}
	
	/**
	 * Replace a property for all objects in this ESCAPE instance.
	 * @param string $sourceProperty source property (RDF predicate)
	 * @param string $targetProperty target property (RDF predicate)
	 * @param string $rdfType RDF type
	 * @param boolean $includeSubTypes if true, sub types are included
	 * @param boolean $preview if true, no actual changes are made
	 * @return array array containing the URIs of the objects modified by this operation
	 */
	public function replaceProperty($sourceProperty, $targetProperty, $rdfType = null, $includeSubTypes = true, $dryRun = false)
	{
		$modifiedObjectUris = array();
		
		// build a SPARQL pattern to limit the results to the RDF type specified
		$sparqlQueryTypeLimit = "";
		if($rdfType !== null && $rdfType !== '')
		{
			$rdfTypes = array();
			if($includeSubTypes)
			{
				// include sub types
				$rdfTypes = $this->repositorySchema->getSubClassUriList($rdfType);
			}
			$rdfTypes[] = $rdfType;
			
			$rdfTypePatterns = array();
			foreach($rdfTypes as $rdfType)
			{
				$rdfTypePatterns[] = '{?s <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <' . $rdfType . '> .}';
			}
			$sparqlQueryTypeLimit = implode(' UNION ', $rdfTypePatterns);
		}
		
		// fetch URIs of objects containing the source property and matching the RDF type specified
		$sparqlQuery  = "select distinct ?s\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?s <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.ore.Object> .\n";
		$sparqlQuery .= "	?s <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "	?s <" . $this->tripleStore->sparqlEscapeUri($sourceProperty) . "> ?o .\n";
		if($sparqlQueryTypeLimit)
		{
			$sparqlQuery .= "	" . $sparqlQueryTypeLimit . " \n";
		}
		$sparqlQuery .= "}\n";
		
		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		foreach($results as $result)
		{
			$uri = $result['s']['value'];

			// fetch the object
			$oreObject = $this->getOreAggregatedResourceByUri($uri);
			
			// get the properties
			$rdfResource = $oreObject->getProperties();
			
			// replace the property
			$objects = $rdfResource->getObjects($sourceProperty);
			$rdfResource->removeStatement($sourceProperty);
			$rdfResource->addStatement($targetProperty, $objects);
			
			//TODO: update lastModifiedBy ?
			
			// store the changes
			if(!$dryRun)
			{
				$oreObject->setProperties($rdfResource);
			}

			// keep track of modified objects
			$modifiedObjectUris[] = $uri;
		}
		
		return $modifiedObjectUris;
	}

	/**
	 * Get a user by URI.
	 * @param string $uri User object URI
	 * @param boolean $noIndex If true, the object is fetched without using the index.
	 * @param boolean $allowDeleted If true, also return deleted objects
	 * @return escape_repository_User user object
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 */
	public function getUserByUri($uri, $noIndex = false, $allowDeleted = false)
	{
		return new escape_repository_User($this, $uri, null, $noIndex, $allowDeleted);
	}

	/**
	 * Get a user by PID.
	 * @param string $pid User PID
	 * @param boolean $noIndex If true, the object is fetched without using the index.
	 * @return escape_repository_User user object
	 * @throws escape_repository_exception_NotFound
	 * @throws escape_repository_exception_TypeMismatch
	 * @deprecated use getUserByUri() instead
	 */
	public function getUserByPid($pid, $noIndex = false)
	{
		$uri = $this->fedora->convertPidToUri($pid);
		return $this->getUserByUri($uri, $noIndex);
	}

	/**
	 * Get all user objects.
	 * @return array List of user objects
	 */
	public function getAllUsers()
	{
		$users = array();
		 
		$sparqlQuery  = "select ?userUri\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?userUri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.escape.User> .\n";
		$sparqlQuery .= "	?userUri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "}";

		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		foreach($results as $result)
		{
			$userUri = $result['userUri']['value']; 
			$users[] = $this->getUserByUri($userUri);
		}
		
		return $users;
	}

	/**
	 * Get user objects by query.
	 * Matches users based on the given query terms (AND).
	 * The current implementation matches any word in either the user's name, ID or organization, starting with a query term.  
	 * @param string $query whitespace separated query terms
	 * @return array List of user objects
	 */
	public function getUsersByQuery($query, $limit)
	{
		$users = array();
		
		$limit = @intval($limit);
		
		$queryTerms = preg_split('/\\s/', $query, -1, PREG_SPLIT_NO_EMPTY);

		if(count($queryTerms) == 0)
		{
			// return an empty list when no query terms are provided
			return $users;
		}
		else if(count($queryTerms) > 10)
		{
			// limit the number of query terms
			$queryTerms = array_slice($queryTerms, 0, 10);
		}

		$sparqlQuery  = "select ?userUri\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	?userUri <info:fedora/fedora-system:def/model#hasModel> <info:fedora/" . $this->config['system_object_namespace'] . ":cmodel.escape.User> .\n";
		$sparqlQuery .= "	?userUri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "	?userUri <" . escape_repository_User::$predicate_name . "> ?name .\n";
		$sparqlQuery .= "	optional { ?userUri <" . escape_repository_User::$predicate_organization . "> ?organization } .\n";
		$sparqlQuery .= "	FILTER (";
		foreach($queryTerms as $queryTermIdx => $queryTerm)
		{
			if($queryTermIdx > 0)
			{
				$sparqlQuery .= " && ";
			}
			
			$userNameRegex = '(^|\\s)' . preg_quote(strtolower($queryTerm));
			$userUriRegex = '^info:fedora/' . $this->config['user_object_namespace'] . ':' . preg_quote(strtolower($queryTerm));
			
			$sparqlQuery .= "(";
			$sparqlQuery .= "regex(?name, \"" . $this->tripleStore->sparqlEscapeLiteral($userNameRegex) . "\", \"i\")";
			$sparqlQuery .= " || ";
			$sparqlQuery .= "regex(str(?userUri), \"" . $this->tripleStore->sparqlEscapeLiteral($userUriRegex) . "\", \"i\")";
			$sparqlQuery .= " || ";
			$sparqlQuery .= "regex(?organization, \"" . $this->tripleStore->sparqlEscapeLiteral($userNameRegex) . "\", \"i\")";
			$sparqlQuery .= ")";
		}
		$sparqlQuery .= ")\n";
		$sparqlQuery .= "}\n";
		if($limit)
		{
			$sparqlQuery .= "LIMIT " . $limit;
		}
		
		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		foreach($results as $result)
		{
			$userUri = $result['userUri']['value']; 
			$users[] = $this->getUserByUri($userUri);
		}
		
		return $users;
	}
	
	/**
	 * Get a user by OpenID.
	 * @param string $openId OpenID
	 * @return escape_repository_User User object or null if no user was found matching the OpenID specified
	 */
	public function getUserByOpenId($openId)
	{
		$user = null;

		$sparqlQuery  = "select ?userUri\n";
		$sparqlQuery .= "where\n";
		$sparqlQuery .= "{\n";
		$sparqlQuery .= "	{\n";
		$sparqlQuery .= "		?userUri <" . escape_repository_User::$predicate_open_id . "> \"" . $this->tripleStore->sparqlEscapeLiteral($openId) . "\" .\n";
		$sparqlQuery .= "		?userUri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
		$sparqlQuery .= "	}\n";
		if(escape_isValidUri($openId))
		{
			$sparqlQuery .= "	union\n";
			$sparqlQuery .= "	{\n";
			$sparqlQuery .= "		?userUri <" . escape_repository_User::$predicate_open_id . "> <" . $this->tripleStore->sparqlEscapeUri($openId) . "> .\n";
			$sparqlQuery .= "		?userUri <info:fedora/fedora-system:def/model#state> <info:fedora/fedora-system:def/model#Active> .\n";
			$sparqlQuery .= "	}\n";
		}
		$sparqlQuery .= "}";

		$sparqlResultSet = $this->tripleStore->sparqlSelect($sparqlQuery);
		$results = $sparqlResultSet->getResults();
		
		if(count($results) > 0)
		{
			$userUri = $results[0]['userUri']['value'];
			$user = $this->getUserByUri($userUri);
		}
		
		return $user;
	}
	
	/**
	 * Create a new user.
	 * @param string $userUri User URI
	 * @return escape_repository_User The newly created user object
	 */
	public function createUser($userUri)
	{
		$user = null;
		
		if($userUri !== null)
		{
			$userPid = $this->fedora->convertUriToPid($userUri);
			
			// create a user
			$userPid = $this->_createDataObject("ESCAPE User", array('info:fedora/' . $this->config['system_object_namespace'] . ':cmodel.escape.User'), $userPid);
			
			// fetch the user object without using the index
			$user = new escape_repository_User($this, $userUri, null, true);
			
			// update the index
			$user->updateIndex();
		}
		
		return $user;
	}
	
	/**
	 * Validate an ID, throws an exception if the ID is invalid.
	 * @param string $id ID
	 * @throws escape_repository_exception_InvalidUri
	 */
	public function validateId($id)
	{
		if(!preg_match('/^[a-z0-9._-]{1,50}$/', $id))
		{
			throw new escape_repository_exception_InvalidUri('The ID specified is not a valid ID.');
		}
	}
	
	/**
	 * Convert a data object ID to a object URI.
	 * @param string $id Data object ID
	 * @return string Object URI
	 * @throws escape_repository_exception_InvalidUri
	 */
	public function convertDataObjectIdToObjectUri($id)
	{
		$this->validateId($id);
		return $this->config['uri_prefix'] . $this->config['data_object_namespace'] . ':' . $id;
	}
	
	/**
	 * Convert an object URI to a data object ID.
	 * @param string $uri Object URI
	 * @return string Data object ID
	 * @throws escape_repository_exception_InvalidUri
	 */
	public function convertObjectUriToDataObjectId($uri)
	{
		$prefix = $this->config['uri_prefix'] . $this->config['data_object_namespace'] . ':';
		if(preg_match('~^' . preg_quote($prefix, '~') . '~', $uri))
		{
			$id = str_replace($prefix, '', $uri);
			$this->validateId($id);
			return $id;
		}
		else
		{
			throw new escape_repository_exception_InvalidUri('The URI specified is not a valid data object URI.');
		}
	}
	
	/**
	 * Convert a user object ID to a object URI.
	 * @param string $id User object ID
	 * @return string Object URI
	 * @throws escape_repository_exception_InvalidUri
	 */
	public function convertUserObjectIdToObjectUri($id)
	{
		$this->validateId($id);
		return $this->config['uri_prefix'] . $this->config['user_object_namespace'] . ':' . $id;
	}
	
	/**
	 * Convert an object URI to a user object ID.
	 * @param string $uri Object URI
	 * @return string User object ID
	 * @throws escape_repository_exception_InvalidUri
	 */
	public function convertObjectUriToUserObjectId($uri)
	{
		$prefix = $this->config['uri_prefix'] . $this->config['user_object_namespace'] . ':';
		if(preg_match('~^' . preg_quote($prefix, '~') . '~', $uri))
		{
			$id = str_replace($prefix, '', $uri);
			$this->validateId($id);
			return $id;
		}
		else
		{
			throw new escape_repository_exception_InvalidUri('The URI specified is not a valid user object URI.');
		}
	}
	
	
	
	/**
	 * Convert a Fedora PID to a Fedora URI.
	 * @param string $pid Fedora PID
	 * @return string Fedora URI
	 * @deprecated use convertPidToUri in the Fedora layer instead
	 */
	public function convertPidToUri($pid)
	{
		return $this->fedora->convertPidToUri($pid);
	}
	
	/**
	 * Convert a Fedora URI to a Fedora PID.
	 * @param string $uri Fedora URI
	 * @return string Fedora PID
	 * @deprecated use convertUriToPid in the Fedora layer instead
	 */
	public function convertUriToPid($uri)
	{
		return $this->fedora->convertUriToPid($uri);
	}

	/**
	 * Convert a data object URI to a data object ID.
	 * @param string $uri Object URI
	 * @return string Object ID
	 * @throws escape_repository_exception_InvalidUri
	 * @deprecated use convertObjectUriToDataObjectId instead
	 */
	public function convertUriToId($uri)
	{
		return $this->convertObjectUriToDataObjectId($uri);
	}
	
}